<!--

/*
* Copyright (C) 2005-2013 University of Sydney
*
* Licensed under the GNU License, Version 3.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/gpl-3.0.txt
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

/**
* updateDetailsFromDelimited.html
* reads a delimited file including record IDs and updates a field with the values provided
*
* @author      Tom Murtagh
* @author      Kim Jackson
* @author      Ian Johnson   <ian.johnson@sydney.edu.au>
* @author      Stephen White   <stephen.white@sydney.edu.au>
* @author      Artem Osmakov   <artem.osmakov@sydney.edu.au>
* @copyright   (C) 2005-2013 University of Sydney
* @link        http://Sydney.edu.au/Heurist
* @version     3.1.0
* @license     http://www.gnu.org/licenses/gpl-3.0.txt GNU License 3.0
* @package     Heurist academic knowledge management system
* @subpackage  !!!subpackagename for file such as Administration, Search, Edit, Application, Library
*/

-->

<html>
	<head>
		<title>Field Updater</title>

		<!--
		This HAPI tool is designed to update details in a heurist record or records of
		a record type. It takes one or more records of format :
		HRecordID, HDetailTypeID, newValue
		It is written generically to allow any non repeatable literal HDetailType.
		The tool reads the record types from Heurist and checks for existance and type
		against the reords in Heurist. Before updating the detail the value is checked
		for validity.
		-->


        <meta http-equiv="content-type" content="text/html; charset=utf-8">
		<script src="../../common/php/loadHAPI.php"></script>         <!--FIXME  sometimes get and error that hapi be loaded before goi, need to send switch to load-hapi to also load gio. -->
		<script src="../../common/php/getMagicNumbers.php"></script>
		<script>
			if (!( HAPI && HAPI.CurrentUser.isLoggedIn())){
				window.location = "../../common/connect/login.php";//saw FIXME: instance code here
			}
		</script>

		<script src="../../hapi/js/goi.js"></script>
		<script src="../../common/js/temporalObjectLibrary.js"></script>
		<script src="../../external/jquery/jquery-1.6.min.js"></script>

		<script>
			const recChunkSize = 100; //1000; // increased from 1000 by ian 24/10/11
			// TODO: Why are we using literals in the code rather than using the constant?
			var fields = [];
			var recTypeSelect;
			var workgroupSelect;
			var workgroups = {};
			var workgroupTags = {};
			var colSelectors = [];
			var cols = [];
			var geoTypes = [];
			var recRecords = [];
			var savRecords = [];
			var references = [];
			var bRecordsLoaded = false;
			var recStart = 0;
			var recEnd = recChunkSize;
			var fMapIndexLookup = ["dd/mm/yyyy","mm/dd/yyyy", "yyyy/mm/dd","yyyy"];
			var fMapIndex=0; //default to dd/mm/yyyy
			var fMap = [["0","1","2"],["1","0","2"],["2","1","0"],["2","zm","zd"]];

            var err={"count":0};
            function logError(key, msg) {
                if (!err[key]) {
                    err[key] = msg;
                }else{
                    err[key] += "\n" + msg;
                }
                err.count ++;
            }

			function stripCharacter(words,character) {
				var spaces = words.length;
				for(var x = 0; x<spaces; ++x){
					words = words.replace(character, "");
				}
				return words;
			}

			/**
			*
			*/
			function startSaveRecords(){
				//hide update butttom
				var e = document.getElementById("process-div");
				e.style.display = "none";

				setTimeout(function() {
					loadPointers();
				},100);
			}

			function loadPointers( ) {
				var i;
				if (recStart >= fields.length){
					showProcessSummary();//FINISH. We are done show the user what happened
					return;
				}
				if (recStart > 0) {
					recStart = recEnd;
					if (recEnd + recChunkSize > fields.length) {
						recEnd = fields.length;
					}	else {
						recEnd = recStart + recChunkSize;
					}
				}else {
					if (recEnd > fields.length) {
						recEnd = fields.length;
					}
				}
				var recIDs = [];
				//get recChunkSize records
				for(i=0; i < (recEnd-recStart); i++) {
					var newIDs = [];
                    function isValidNewID(index){
                        if ( !isNaN(index)) {
                            var id = parseInt(index);
                            if (!HeuristScholarDB.getRecord(id) && !(recIDs.indexOf(id)>=0) && !(newIDs.indexOf(id)>=0)){
                                return true;
                            }
                        }
                        return false;
                    }
					// flatten the recordID
					if ( isValidNewID(fields[i+recStart][0])) { //if new value
						newIDs.push(parseInt(fields[i+recStart][0])); //store the Record ID
					}
					if (!isNaN(fields[i+recStart][1]) && HDetailManager.getDetailTypeById(parseInt(fields[i+recStart][1])).getVariety()==HVariety.REFERENCE) { // updating a reference detail load referenced object
						var vals = fields[i+recStart][2].split("|");
						for(v in vals){
							if (isValidNewID(vals[v])){
								newIDs.push(parseInt(vals[v]));
							}
						}
						// case - replace existing value with a new value - redirecting a reference
						if (fields[i+recStart][3] && isValidNewID(fields[i+recStart][3])){
							newIDs.push(parseInt(fields[i+recStart][3]));
						}
					}
					if ((newIDs.length + recIDs.length)>recChunkSize){ // increased from 1000 by Ian 24/10/11
						//adjust recEnd and break out
						recEnd = i + recStart;
						break;
					}else{
						recIDs = recIDs.concat(newIDs);
					}
				} //for i
//				console.debug("loading records " + recIDs.join());
				if (recIDs.length > recChunkSize) { // if over by 10% then error out
                    // the chunks are now processed automatically, so you shouldn't get this message
					alert("This is embarrassing, but somehow you have exceeded ("+ recIDs.length +
							") the capacity ("+recChunkSize+" per transfer) of the updater to handle. We need to start again. The last row handled was " + recStart);
					location.reload();
				}

				var loader = new HLoader(
				function(s,r) {
					recRecords = r;
					document.getElementById("info-p").innerHTML = "Processing input rows " + (recStart+1) + " to " + recEnd+" of "+fields.length;
					updateDetails();
				},
				function(s,e) {
					alert("load failed: " + e);
				});
				var mysearch = new HSearch("ids:" + recIDs.join());

				HeuristScholarDB.setDefaultLoadRecordLimit(recChunkSize); //set the limit to the max, increased from 1000 by Ian 24/10/11
				if (HeuristScholarDB.getDefaultLoadRecordLimit() != recChunkSize){
					alert("record limit not properly set."); //set the limit to the max, increased from 1000 by Ian 24/10/11
				}
				HeuristScholarDB.loadRecords(mysearch,loader);  //asynchronous call for record data
			}

			function updateDetails() {
				var recHR;
				var recHRT;
				var recHDT;
				var recHDV;
				var details;
				var saver = new HSaver(
				function(r) {
//					console.debug("saved records" + r);    //FIXME  write code to valid field value and update fields array with success message
					//document.getElementById("info-p").innerHTML = "Processing input rows " + (recStart+1) + " to " + recEnd;// + " of "  + recIDs.length;
					recStart = recEnd;
					setTimeout(function(){
							loadPointers();},
					100);
				},
				function(r,e) {
					alert("record save failed: " + e);     //FIXME write code to put error message in fields array.
				});

				//clear any previous save records
				while (savRecords.length > 0) {savRecords.pop();};

				for (j = recStart; j <recEnd ; j++) {
					if(!isNaN(fields[j][0])) { //record input
						//get the HRecord to be updated
						recHR = HeuristScholarDB.getRecord(parseInt(fields[j][0]));

						if (! recHR) { //record not loaded skip it
                            logError(j," record "+ fields[j][0] +" on line "+j+"not loaded into Hapi");
							continue;
						}

						//get the HRecordType
						recHRT = recHR.getRecordType();

						if( isNaN(fields[j][1])) {
							if( (fields[j][1]).toLowerCase()=="tags"){
								var myTags =fields[j][2];
								if (myTags.indexOf("\"")>=0){
									myTags = myTags.replace(/"/g,""); // strip off extra quotes
								}
								myTags = myTags.split("|");  // separate if multiple tags
								for (var t in myTags) {
									HTagManager.addTag(myTags[t]);	// ensure the tag exists
									recHR.addTag(myTags[t]);
								}
								if (!(savRecords.indexOf(recHR)>=0) ) {// add this record to list for saving
									savRecords.push(recHR);
								}
							}else if ((fields[j][1]).toLowerCase() == "url") {
								recHR.setURL(fields[j][2]);
								if (!(savRecords.indexOf(recHR)>=0) ) {// add this record to list for saving
									savRecords.push(recHR);
								}
							} else if ((fields[j][1]).toLowerCase() == "notes") {
								recHR.setNotes(fields[j][2]);
								if (!(savRecords.indexOf(recHR)>=0) ) {// add this record to list for saving
									savRecords.push(recHR);
								}
							}
						}else{//get the detail type
							recHDT = HDetailManager.getDetailTypeById(parseInt(fields[j][1]));
							if (recHDT){ //valid detail type
								if (fields[j][2]!=null && fields[j][2]!="") {

									switch( recHDT.getVariety()){

										// change data to a date object for validity code. FIXME need to do the same for GEOGRAPHIC, FILE details
										case HVariety.GEOGRAPHIC:
										    var vals = fields[j][2].split("|");
										    var geoFields;
										    var geo;
										    details = recHR.getDetails(recHDT);
										    if (HDetailManager.getDetailRepeatable(recHRT, recHDT )) {
											    // update/add the detail if the value exist and is valid
											    for (var v in vals) {
												    geoFields = vals[v].split(":");
												    geoFields[0] = geoFields[0].match(/^(\s*)(\S)(\s*)/)[2]; //strip any spaces
												    geo = new HGeographicValue(geoFields[0], geoFields[1]);
												    if (recHDT.checkValue(geo)) {
													    details.push(geo);
												    } else {
													    //  FIXME: add code to show invalid entry in error bin
													     logError(j,"the value" + vals[v] + " is not valid for " + fields[j][1] + " detail type specified.  " + fields[j].join());
												    }
											    }
											    recHR.setDetails(recHDT, details);
										    } else { //singleton  case  update/add
											    geoFields = vals[0].split(":");
											    geoFields[0] = geoFields[0].match(/^(\s*)(\S)(\s*)/)[2]; //strip any spaces
											    geo = new HGeographicValue(geoFields[0], geoFields[1]);
											    if (!details[0]) {
												    recHR.addDetail(recHDT, geo);
											    } else {
												    recHR.changeDetail(recHDT, details[0], geo);
											    }
										    }
										    break;

										case HVariety.FILE:

											var newval = fields[j][2];
						 					this.initFile = this.updateDetails;
				 							ffile = new HFile(this, 0, '', 0, null, newval, null, ''); //new value
						 					delete this.initFile;

											details = recHR.getDetails(recHDT);
											if (!details[0]) {
												recHR.addDetail(recHDT, ffile);
											} else {
												recHR.changeDetail(recHDT, details[0], ffile);
											}

										break;

										case HVariety.DATE:
										    details = recHR.getDetails(recHDT);
										    if (fields[j][3]) {	//there is an old value supplied this is a replacement
											    recHR.changeDetail(recHDT, fields[j][3], fields[j][2]);
										    }else if (details && details.length < 1 || HDetailManager.getDetailRepeatable(recHRT, recHDT )){
											    recHR.addDetail(recHDT, fields[j][2]);
										    }else if (details && details[0]){
												    recHR.changeDetail(recHDT, details[0], fields[j][2]);
										    }else{
                                                recHR.addDetail(recHDT, fields[j][2]);
                                            }
										    break;

										case HVariety.REFERENCE:
										    var recDetailIDs = [];
										    var ids = fields[j][2].split("|"),
                                                i,d, recDTConst,
                                                newRefHR,
                                                validIDs = [];
										    details = recHR.getDetails(recHDT);
										    for ( d in details){//details lookup by recID
											    recDetailIDs[details[d].getID()] = d;
										    }
                        // Add code to check type constraints if constraints exist
                        //get HRec is not logError and skip
                        //get HRT in not match then remove id and logError
                        if (recHDT.getConstrainedRecordTypeIDs &&    //check to make sure the record matches constraints
                            recHDT.getConstrainedRecordTypeIDs() &&
                            recHDT.getConstrainedRecordTypeIDs().length > 0) {
                            recDTConst = recHDT.getConstrainedRecordTypeIDs();
                            for (i in ids) {//if rtyID is in constraint set the put in valid array
                                newRefHR = HeuristScholarDB.getRecord(ids[i]);
                                if (!newRefHR) {
                                    logError(j,"linked record id not found");
                                }else if (recDTConst.indexOf(""+newRefHR.getRecordType().getID()) != -1){
                                    //add id
                                    validIDs.push(ids[i]);
                                }else{
                                    logError(j,"rectype not in constraint set ignoring input");
                                }
                            }
                            ids = validIDs;
                        }
										    if (HDetailManager.getDetailRepeatable(recHRT, recHDT )) {//repeatable field
											    for(i in ids){
												    if (!isNaN(ids[i])){
                                                        newRefHR = HeuristScholarDB.getRecord(ids[i]);
                                                        if (!newRefHR) {
                                                            logError(j,"rec not found");
                                                        }else if ( !isNaN(fields[j][3])){ // if there is a replacement value  HRecordID, HDT, newRefID, oldRefID
														    if (fields[j][3] in recDetailIDs){ //the old ref is a details
															    if (!(ids[i] in recDetailIDs)){// the new value is not then replace
																    recHR.changeDetail(recHDT, HeuristScholarDB.getRecord(fields[j][3]), HeuristScholarDB.getRecord(ids[i]));
															    }else if (confirm("Trying to replace "+recHDT.getName()+"("+recHDT.getID()+") repeatable link ID "+
                                                                                    fields[j][3] + "-" +HeuristScholarDB.getRecord(fields[j][3]).getTitle() +
                                                                                    " with ID " + ids[i] + "-" + HeuristScholarDB.getRecord(ids[i]).getTitle() +"." +
                                                                                    "\nSince ID "+ids[i]+" already exist, would you like to just remove ID "+
                                                                                    fields[j][3] +"?")){  // the new value already exist so delete the old
																    recHR.removeDetails(recHDT);//TODO:SAW this needs to be replaced with a delete detail so that we minimize the changing of ids for future pointers to details.
																    details.splice(recDetailIDs[fields[j][3]],1);  //remove the old from details
																    recHR.setDetails(recHDT,details);
															    }else{
                                                                    logError(j,"user cancelled delete of "+fields[j][3]+" from "+recHDT.getName());
                                                                }
														    }else {	// old ref doesn't exist so just add the new ref if it doesn't exist
															    if (!(ids[i] in recDetailIDs)) recHR.addDetail(recHDT,HeuristScholarDB.getRecord(ids[i]));
														    }
													    }else{	// if it's not already a detail then add it
														    if (!(ids[i] in recDetailIDs)) recHR.addDetail(recHDT,HeuristScholarDB.getRecord(ids[i]));
													    }
												    }
											    }
										    }else{//non repeatable
											    if (ids.length=1 && !recDetailIDs[ids[0]]){ //different id
												    if (details && details.length > 0 && details[0]){//update if there is an existing detail
                              recHR.changeDetail(recHDT, details[0], HeuristScholarDB.getRecord(ids[0]));
/*
													    if (fields[j][3] && !isNaN(fields[j][3])){
														    recHR.changeDetail(recHDT, details[0], HeuristScholarDB.getRecord(fields[j][3]));
													    }else{
														    logError(j,"update failed on non matching existing value " + fields[j][3]);
													    }
*/
                            }else{//add
													    recHR.addDetail(recHDT,HeuristScholarDB.getRecord(ids[0]));
												    }
											    }else{//multi-value
												    //FIXME: add code to place this row in error bin
											    }
										    }
										    break;

										default:
										    var vals=fields[j][2].split("|");
										    details = recHR.getDetails(recHDT);
										    if (HDetailManager.getDetailRepeatable(recHRT, recHDT )) {
											    // update/add the detail if the value exist and is valid
											    for (var v in vals) {
												    if (recHDT.checkValue(vals[v])) {// if not in the list of detail values
													    if (!(details.indexOf(vals[v])>=0))  {
														    details.push(vals[v]);
													    }
												    }else{
													    //  show invalid entry in error bin
						                                logError(j,"the value" + vals[v] +" on line "+j+ " is not valid for " + fields[j][1] + " detail type specified.  " + fields[j].join());
												    }
											    }
											    recHR.setDetails(recHDT, details);
										    }else{ //singleton  case  update/add
											    if (!details[0]) {
												    recHR.addDetail(recHDT, vals[0]);
											    }else{
												    recHR.changeDetail(recHDT, details[0], vals[0]);
											    }
										    }
									}//end switch
									// add the record to the list of records to save
									if (!(savRecords.indexOf(recHR)>=0)) {
										savRecords.push(recHR);
									}
								}else{  // null value interpret as delete detail
                                    //check for multivalue delete
                                    //TODO: this needs to handle multiple values for repeatable fields
                                    //right now assume multi for repeatable pointers
                                    var dels=fields[j][3].split("|");
                                    var recDetailIDs = []
                                        i,d;
                                    details = recHR.getDetails(recHDT);
                                    for ( d in details){//details lookup by recID
                                        if (recHDT.getVariety() == HVariety.REFERENCE) {
                                            recDetailIDs[details[d].getID()] = d;
                                        }else{
                                            recDetailIDs[details[d]] = d;
                                        }
                                    }
                                    if (dels.length >= 1 && HDetailManager.getDetailRepeatable(recHRT, recHDT )) {
                                        // remove the detail if the value exist
                                        for (var i in dels) {
                                            if (dels[i] in recDetailIDs) {
                                                details.splice(recDetailIDs[dels[i]],1);
                                                delete recDetailIDs[dels[i]];
                                                for ( d in details){//details index lookup by recID needs to recalc
                                                    if (recHDT.getVariety() == HVariety.REFERENCE) {
                                                        recDetailIDs[details[d].getID()] = d;
                                                    }else{
                                                        recDetailIDs[details[d]] = d;
                                                    }
                                                }
                                            } else {
                                                 logError(j,"the value" + dels[i] +" on line "+j+ " was not found as a value of detail type(" + fields[j][1] + ") specified.  " + fields[j].join());
                                            }
                                        }
                                        if (details.length > 0){
                                            recHR.setDetails(recHDT, details);
                                        } else {//down to no details so just remove all
                                            recHR.removeDetails(recHDT);
                                        }
                                    }else{ //singleton
                                        recHR.removeDetails(recHDT);
                                    }
                                    if (!(savRecords.indexOf(recHR)>=0)) {
                                        savRecords.push(recHR);
                                    }
								}	//end null value
							}else{ //end if valid Detail type
                                logError(j,"detail type "+fields[j][1]+" on line "+j+" was not found in Hapi");
                            }
						} // process detail
					}else{  // command input   dateformat,yyyy
						if (fields[j][0].toLowerCase() == "dateformat") {
							if (!fields[j][1] ){
								fMapIndex=0;
							}else if (fMapIndexLookup.indexOf(fields[j][1])>=0){
								fMapIndex=fMapIndexLookup.indexOf(fields[j][1]);
							}
						}
					}
				}  //for j = recStart

				//save the records to effect the changes
				//console.debug("saving records " + savRecords);
				HeuristScholarDB.saveRecords(savRecords, saver);
//				recStart = recEnd;
				//make a timer call letting this fucntion complete in order to unroll the stack.
//				setTimeout(function(){loadPointers();},100);
			}



			function analyseCSV() {
				var separator = document.getElementById("csv-separator").value;
				var terminator = document.getElementById("csv-terminator").value;
				var quote = document.getElementById("csv-quote").value;
				var lineRegex, fieldRegex, doubleQuoteRegex;

				if (terminator == "\\n") terminator = "\n";

				var switches = (terminator == "\n") ? "m" : "";

				if (quote == "'") {
					lineRegex = new RegExp("[^" +terminator + "]+(?=(?:" + terminator +
					"[^']*'[^']*')*(?!"+terminator+"[^']*')|$)", "g" + switches);
					fieldRegex = new RegExp(separator + "(?=(?:[^']*'[^']*')*(?![^']*'))"), switches;
				} else {	// find non-terminator characters followed by a terminator that is
					// followed by a string where quotes are matched or followed by the end of input
					lineRegex = new RegExp("[^" +terminator + "]+(?=(?:" + terminator +
					"[^\"]*\"[^\"]*\")*(?!"+terminator+"[^\"]*\")|$)", "g" + switches);
					fieldRegex = new RegExp(separator + "(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))", switches);
				}
				doubleQuoteRegex = new RegExp(quote + quote, "g");

				var lines = document.getElementById("csv-textarea").value.match(lineRegex);
				for (var i in lines) {
					fields[i] = lines[i].split(fieldRegex);
					for (var j in fields[i]) {
						fields[i][j] = fields[i][j].replace(doubleQuoteRegex, quote);
					}
				}

				var div = document.getElementById("csv-entry-div");
				div.parentNode.removeChild(div);

				showDetails()
			}

			function showProcessSummary() {

				var log = document.getElementById("log");
				log.innerHTML = "";
				var table = document.createElement("table");
				table.id = "detail-input-table";
				tr = table.appendChild(document.createElement("tr"));
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "row #"; //row number
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "HRecord ID"; //record ID
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "HDetail ID"; //HDetail ID
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "Update Value(s)"; //value or list of values for a repeatable field
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "Error and/or warnings"; // old value to replace

				var errorsOrWarningExist = false;
				for (var i = 0; i < fields.length; ++i) {
					if(!isNaN(fields[i][0])) { //record input
						//get the HRecord updated
						recHR = HeuristScholarDB.getRecord(fields[i][0]);
						if (! recHR && !err[i]) {
							continue;
						}else if (err[i] || recHR.hasError() || recHR.hasWarning()) {
							var tr = table.appendChild(document.createElement("tr"));
							var td = tr.appendChild(document.createElement("td"));
							td.innerHTML = i + 1; //row number
							for (var j =0; j<3; j++) {
								td = tr.appendChild(document.createElement("td"));
								td.appendChild(document.createTextNode(fields[i][j]));
							}
							td = tr.appendChild(document.createElement("td"));
							var errors = recHR.getError();
							if (errors && errors.length) {
								errorsOrWarningExist = true;
								para = document.createElement('pre');
								para.innerHTML = "Error(s):\n" + errors.join(". \n");
								td.appendChild(para);
							}
							var warnings = recHR.getWarning();
							if (warnings && warnings.length) {
								errorsOrWarningExist = true;
								para = document.createElement('pre');
								para.innerHTML = "Warning(s):\n" + warnings.join(". \n");
								td.appendChild(para);
							}
                            if (err[i]) {
                                errorsOrWarningExist = true;
                                para = document.createElement('pre');
                                para.innerHTML = "Input errors:\n" + err[i];
                                td.appendChild(para);
                            }
						}
					}
				}
				if (errorsOrWarningExist) { // there were problems
					log.appendChild(table);
					log.style.display = "block";
				} else { // The detail update completed successfully
                /*  Unecessary option: if (confirm("Update successful, " + fields.length + " entries processed. \n" +"Would you like to use DetailUpdater for more updating?")){location.reload();}else{window.close();}   */
                alert("Update successful, " + fields.length + " entries processed" );
                window.close();
				}
			}

			function showDetails(){
				var e = document.getElementById("process-div");
				e.style.display = "block";

				var p = document.getElementById("records-p");

				var table = document.createElement("table");
				table.id = "detail-input-table";
				tr = table.appendChild(document.createElement("tr"));
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "row #"; //row number
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "HRecord ID"; //record ID
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "HDetail ID"; //HDetail ID
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "Update Value(s)"; //value or list of values for a repeatable field
				td = tr.appendChild(document.createElement("td"));
				td.innerHTML = "Old Value"; // old value to replace

				// create rest of table filling it with the csv analysed data
				for (var i = 0; i < fields.length; ++i) {
					tr = table.appendChild(document.createElement("tr"));
					td = tr.appendChild(document.createElement("td"));
					td.innerHTML = i + 1; //row number
					// FIXME : write code here to check for proper input date and write error message for erronious input
					for (var j in fields[i]) {
						td = tr.appendChild(document.createElement("td"));
						//strip quotes if necessary
						if (fields[i][j].toString().match("\"")){
							fields[i][j] = (stripCharacter(fields[i][j], "\""));
						}
						td.appendChild(document.createTextNode(fields[i][j]));

					}
				}

				p.appendChild(table);
			}
			function popupFieldTypes(){
				top.HEURIST.search.popupLink(top.HEURIST.baseURL+"admin/structure/fields/manageDetailTypes.html?db="+top.HEURIST.database.name, "wide");
			}


		</script>
		<link rel="stylesheet" type="text/css" href= "../../common/css/global.css">
		<link rel="stylesheet" type="text/css" href= "../../common/css/admin.css">
		<style>
			#csv-textarea {
				width: 100%;
				height: 400px;
				border: 1px solid #6A7C99;
			}
			#csv-separator, #csv-terminator { width: 30px; }
			table { border-collapse: collapse; }
			td { width: 100px; border: 1px solid gray; padding: 2px; }
			td select { width: 100px; }
			td.error { color: red; }
			#col-select-row td { border: none; padding: 1px 5px 5px 0px; vertical-align: top; }
			#theList {top:10px; bottom:10px; right:10px; left:auto; overflow:auto;display:none}
			#fieldList {padding:0 10px}
			#fieldTypes {display:inline; clear:none; color:blue; width:20px}
			#fieldTypes:hover div#toolTip2 {visibility:visible; opacity:1}
		</style>
		<link rel=stylesheet href="../../common/css/global.css" media="all">
		<link rel=stylesheet href="../../common/css/admin.css" media="all">
	</head>

	<body class="popup">

		<div id="theList" class="tooltip"><div class="close-button fieldsToggle" style="right:3px; top:3px"></div><ul id="fieldList"><h3>Field Types</h3></ul></div>
		<p id=info-p></p>
        <div id="log"></div>
		<div id=process-div style="display:none;">
			<p>The details shown below will be used to update records in the database.
            <br>Please review before proceeding. Note that erroneous input rows will be skipped.</p>
			<input  type=button value="Update database" onClick="startSaveRecords();" >
			<p id=records-p></p>
		</div>
		<div id=csv-entry-div>
			<p>This function reads CSV data and updates specified fields in records designated by their Heurist record IDs.<br>
			The CSV structure is: <i>Record ID, Internal field code, Value to be inserted</i></p>

			<code>5,365,"Sydney Opera House"</code><br/>
			<code>5,373,41</code><br>
			<code>6,373,41</code><br>
			<code>etc ...</code>

			<p>

			The internal field codes are shown on the left of the Field Types listing in database administration- see <a href="#" id="fieldTypes" class="fieldsToggle">Field types</a></p>

			Values supplied are added to any existing values for the field. Multiple values can be separated by | (pipe) characters.<br>
			Values for record pointer fields should be the id of the record to which the pointer points.<br>
			Geographic data are represented as a geographic type identifier plus OpenGIS WKT data, eg. <code>p:POINT(134.657 32.326)</code><br>

				<!--
				Multiple geographic objects per column can be separated by | (pipe) characters, eg. <code>p:POINT(134.657 32.326)|p:POINT(134.756 32.378)|</code>...<br>
			-->

			Geographic type identifiers are: <code>p:POINT</code> for point, <code>r:POLYGON</code> for rectangle,
				   <code>c:LINESTRING</code> for circle,  <code>pl:POLYGON</code> for polygon,
					<code>l:LINESTRING</code> for polyline
			<p>Tip: To rapidly update a subset of records, use the delimited export function with a query string,
			select the field to be updated, and check the box "Include internal field code in column preceding values".
			This will output the record IDs and field code, allowing values to be added to the end of each row of data and imported using this function.

			<div><b>Paste CSV file in the box below and click <i>Analyse</i></b>
				<p>
				&nbsp;&nbsp;Field separator: <input id=csv-separator value=",">
				&nbsp;&nbsp;Line separator: <input id=csv-terminator value="\n">
				&nbsp;&nbsp;Quote: <select id=csv-quote><option selected>"</option><option>'</option></select>
				&nbsp;&nbsp;&nbsp;&nbsp;
				<input type=button value=Analyse onClick="analyseCSV();">
			</div>
			<div><textarea id=csv-textarea></textarea></div>
			<script>
				var fieldList = document.getElementById("fieldList");
				for (var attr in top.HEURIST.detailTypes.typedefs)
				{if(!isNaN(Number([attr])))
				{
				listItem = fieldList.appendChild(document.createElement("li"));
				listItem.innerHTML = "<span style=\"margin-right:10px;\">"+top.HEURIST.detailTypes.typedefs[attr].commonFields[0] + "</span><span>" + top.HEURIST.detailTypes.typedefs[attr].commonFields[1] + "</span>";
					}
				}

				$('.fieldsToggle').click(function() {
					$('#theList').toggle();
				});
			</script>
		</div>
	</body>
</html>
